package com.zoe.onelink.core.web.response;

import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.TypeUtil;
import cn.hutool.http.ContentType;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.zoe.onelink.annotation.web.response.ResultWrapper;
import com.zoe.onelink.common.constant.RpcClient;
import com.zoe.onelink.common.constant.RpcConstant;
import com.zoe.onelink.common.entity.ResultVO;
import com.zoe.onelink.common.model.ApiResult;
import com.zoe.onelink.common.model.IResult;
import com.zoe.onelink.core.property.OnelinkProperties;
import com.zoe.onelink.core.web.response.trace.TraceIdProvider;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.lang.reflect.Type;

/**
 * <p>标题: 返回结果统一包装</p>
 * <p>描述: 这里没有默认对所有Controller进行包装,而是使用自定义注解@ResultWrapper来标记需要包装ResultVO的Controller类
 * 因为有部分第三方框架自带Controller接口,例如swagger
 * </p>
 * <p>版权: Copyright (c) 2021</p>
 * <p>公司: 智业软件股份有限公司</p>
 *
 * @version: 1.0
 * @author: ruansheng
 * @date: 2021-07-28
 */
@RestControllerAdvice(annotations = ResultWrapper.class, basePackages = "com.zoe")
@Order(0)
public class ResultWrapperResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    private final boolean enabled;
    private final TraceIdProvider traceIdProvider;

    public ResultWrapperResponseBodyAdvice(OnelinkProperties onelinkProperties, ObjectProvider<TraceIdProvider> traceIdProvider) {
        this.enabled = onelinkProperties.getWeb().getResponse().isWrapped();
        this.traceIdProvider = traceIdProvider.getIfAvailable();

    }

    @Override
    public boolean supports(MethodParameter methodParameter,
                            @NonNull Class<? extends HttpMessageConverter<?>> clazz) {

        Type type = methodParameter.getExecutable().getAnnotatedReturnType().getType();
        // 返回结果类型
        Class<?> retType = TypeUtil.getClass(type);
        return this.enabled
                // 下载文件或特殊需求返回结果可以使用ResponseEntity
                && retType != ResponseEntity.class;
    }

    @Override
    public Object beforeBodyWrite(Object responseBody,
                                  @NonNull MethodParameter methodParameter,
                                  @NonNull MediaType mediaType,
                                  @NonNull Class<? extends HttpMessageConverter<?>> clazz,
                                  @NonNull ServerHttpRequest serverHttpRequest,
                                  @NonNull ServerHttpResponse serverHttpResponse) {

        HttpHeaders reqHeaders = serverHttpRequest.getHeaders();
        String disableWrapperFlag = reqHeaders.getFirst(ResultWrapper.DISABLE_WRAPPER_HEADER_KEY);
        String rpcClient = reqHeaders.getFirst(RpcConstant.RPC_CLIENT_HEADER_NAME);
        if (this.couldSkip(mediaType, disableWrapperFlag, rpcClient)) {
            return responseBody;
        }
        Type type = methodParameter.getExecutable().getAnnotatedReturnType().getType();
        String traceId = this.traceIdProvider == null ? null : this.traceIdProvider.getTraceId();
        Object result;
        // 远程调用直接返回
        if (responseBody instanceof ApiResult<?>) {
            result = responseBody;
            // 为返回结果设置链路ID
        } else if (responseBody instanceof IResult) {
            ResultVO<?> resultVO = (ResultVO<?>) responseBody;
            result = StrUtil.isBlank(resultVO.getTraceId()) ? resultVO.setTraceId(traceId) : resultVO;
            // 如果返回结果是字符串,不能直接返回ResultVO,否则会与StringHttpMessageConverter冲突
        } else if (responseBody instanceof String || type == String.class) {
            ResultVO<?> resultVO = ResultVO.success(responseBody).setTraceId(traceId);
            result = JSON.toJSONString(resultVO, SerializerFeature.WriteMapNullValue);
            serverHttpResponse.getHeaders().add("content-type", ContentType.JSON.toString());
            // 没有被IResult包装,默认使用ResultVO进行包装
        } else {
            result = ResultVO.success(responseBody).setTraceId(traceId);
        }
        return result;
    }

    private boolean couldSkip(MediaType mediaType, String disableWrapperFlag, String rpcClient) {
        // 只处理application/json类型的返回结果,如果请求头中的disable-wrapper=1,则表示禁用该包装器,直接返回原始结果
        return (!MediaType.APPLICATION_JSON.equals(mediaType) || ResultWrapper.DISABLE.equals(disableWrapperFlag))
                // 不是Feign客户端
                && !RpcClient.FEIGN.name().equals(rpcClient);
    }
}
